/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.remoting.support;

import java.lang.reflect.InvocationTargetException;

/**
 * Abstract base class for remote service exporters that are based
 * on deserialization of {@link RemoteInvocation} objects.
 *
 * <p>Provides a "remoteInvocationExecutor" property, with a
 * {@link DefaultRemoteInvocationExecutor} as default strategy.
 *
 * @author Juergen Hoeller
 * @see RemoteInvocationExecutor
 * @see DefaultRemoteInvocationExecutor
 * @since 1.1
 */
public abstract class RemoteInvocationBasedExporter extends RemoteExporter {

    private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor();


    /**
     * Set the RemoteInvocationExecutor to use for this exporter.
     * Default is a DefaultRemoteInvocationExecutor.
     * <p>A custom invocation executor can extract further context information
     * from the invocation, for example user credentials.
     */
    public void setRemoteInvocationExecutor(RemoteInvocationExecutor remoteInvocationExecutor) {
        this.remoteInvocationExecutor = remoteInvocationExecutor;
    }

    /**
     * Return the RemoteInvocationExecutor used by this exporter.
     */
    public RemoteInvocationExecutor getRemoteInvocationExecutor() {
        return this.remoteInvocationExecutor;
    }


    /**
     * Apply the given remote invocation to the given target object.
     * The default implementation delegates to the RemoteInvocationExecutor.
     * <p>Can be overridden in subclasses for custom invocation behavior,
     * possibly for applying additional invocation parameters from a
     * custom RemoteInvocation subclass. Note that it is preferable to use
     * a custom RemoteInvocationExecutor which is a reusable strategy.
     *
     * @param invocation   the remote invocation
     * @param targetObject the target object to apply the invocation to
     * @return the invocation result
     * @throws NoSuchMethodException     if the method name could not be resolved
     * @throws IllegalAccessException    if the method could not be accessed
     * @throws InvocationTargetException if the method invocation resulted in an exception
     * @see RemoteInvocationExecutor#invoke
     */
    protected Object invoke(RemoteInvocation invocation, Object targetObject)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        if (logger.isTraceEnabled()) {
            logger.trace("Executing " + invocation);
        }
        try {
            return getRemoteInvocationExecutor().invoke(invocation, targetObject);
        } catch (NoSuchMethodException ex) {
            if (logger.isDebugEnabled()) {
                logger.warn("Could not find target method for " + invocation, ex);
            }
            throw ex;
        } catch (IllegalAccessException ex) {
            if (logger.isDebugEnabled()) {
                logger.warn("Could not access target method for " + invocation, ex);
            }
            throw ex;
        } catch (InvocationTargetException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Target method failed for " + invocation, ex.getTargetException());
            }
            throw ex;
        }
    }

    /**
     * Apply the given remote invocation to the given target object, wrapping
     * the invocation result in a serializable RemoteInvocationResult object.
     * The default implementation creates a plain RemoteInvocationResult.
     * <p>Can be overridden in subclasses for custom invocation behavior,
     * for example to return additional context information. Note that this
     * is not covered by the RemoteInvocationExecutor strategy!
     *
     * @param invocation   the remote invocation
     * @param targetObject the target object to apply the invocation to
     * @return the invocation result
     * @see #invoke
     */
    protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
        try {
            Object value = invoke(invocation, targetObject);
            return new RemoteInvocationResult(value);
        } catch (Throwable ex) {
            return new RemoteInvocationResult(ex);
        }
    }

}
